/*!
 * A copy of this code can be obtained from:
 * http://code.google.com/p/core-js/
 *
 * The MIT License
 *
 * Copyright (c) 2009 Jyrki Laurila
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
;(function(core) {
    /**
     * This is the chain constructor.
     * The chain function is meant to provide an effective way to queue or chain synchronous and asynchronous
     * functions to be executed in a specific order. With asynchronous calls, you will have to use the
     * link function to wrap your callback function, so that the queue gets invoked and continues with the queue.
     * <br /><br />
     * When you first create the new chain instance and start queuing items in it, the actual execution process
     * will not start until you explicitly call the execute function. After the execute function is called, the
     * queue is in continuous execution mode. After the queue is empty, the continuous queue will resume automatically
     * after a new queue item is added. You can halt the queue processing by calling the pause function, and resume
     * again by calling the resume function.
     * <br /><br />
     * I've tried to write as much examples as possible about how many ways this module can be used.
     * <br />
     *
     * @example
     * var myQueue = new core.chain(true, function() {
     *     // do stuff, will be executed first
     * }, function() {
     *     // do some more stuff, will be executed second
     * });
     *
     * var myFunc = myQueue.link(function() {
     *     // do even more stuff, will be executed fourth
     *     // Notice that we are just creating a wrapper function to be queued later
     * });
     *
     * myQueue.queue(true, function() {
     *     // do stuff, will be executed third
     * });
     *
     * myQueue.queue(myFunc);
     * 
     * // This will execute all the queued functions with 1000ms throttle
     * myQueue.execute(1000);
     *
     * myQueue.queue(true, function() {
     *     // this function will be executed right after the fourth function finishes
     * });
     *
     * @namespace Holds methods related to chain module
     * @constructor
     * @name core.chain
     * @version $v1.0.0 2009-08-18
     * @author Jyrki Laurila
     * @param {mixed} [1..n] The chain queue can be intitialized through the constructor as well.
     * @see core.chain.queue for parameters
     */
    var chain = function() {
        var queue = [], throttle = null, started = false,
        
        /**
         * Helper function that is used to add items in the queue.
         *
         * @private
         * @param {array} arg An array or an array like object to be processed and added to the queue.
         * @see core.chain.queue for the parameter structure.
         */
        init = function(arg) {
            var i = 0, len = arg.length, item, wrap = false;
            if (arg[0] === true) {
                i = 1;
                wrap = true;
            }
            for (; i < len; i++) {
                item = arg[i];
                if (item.length) {
                    if (wrap) {
                        item.unshift(wrap);
                    }
                    init(item);
                } else {
                    item = (wrap ? link(item) : item);
                    queue.push(item);
                }
            }
        },
        
        /**
         * Invokes the next item in the queue
         *
         * @private
         * @param {number} [wait] A throttle time in milliseconds for this individual call
         */
        invoke = function(wait) {
            if (started) {
                var cur = queue.shift();
                wait = (wait || throttle);
                if (cur) {
                    setTimeout(function() {
                        cur.call();
                    }, wait);
                }
            }
        },
        
        /**
         * @private
         * @param {function} fn Function to be wrapped
         * @param {object} [scope] The scope where the given function will be executed
         * @returns {function} Wrapped function
         */
        link = function(fn, scope) {
            return function() {
                fn.apply(scope, arguments);
                invoke();
            };
        },
        
        /**
         * @private
         * @param {number} [delay] Throttle delay in milliseconds
         * @returns {object} This object instance
         */
        execute = function(delay) {
            throttle = (delay || throttle || 0);
            if (!started) {
                started = true;
                invoke();
            }
        };
        
        /**
         * Wraps the given function inside own function that will invoke the next item in the queue after the
         * given function is executed.
         *
         * @example
         * var myQueue = new core.chain();
         * var myFunc = myQueue.link(function() {
         *     // do stuff
         * });
         * 
         * // This will queue the function and start the queue process
         * myQueue.queue(myFunc).execute();
         *
         * @name core.chain.link
         * @methodOf core.chain
         * @param {function} fn Function to be wrapped
         * @param {object} [scope] The scope where the given function will be executed
         * @returns {function} Wrapped function
         */
        this.link = link;
        
        /**
         * Queues more items to the chain instance
         *
         * @example
         * var myQueue = new core.chain();
         *
         * myQueue.queue(true, function() {
         *     // do stuff, will be executed first
         * }, function() {
         *     // do some more stuff, will be executed second
         * });
         *
         * myQueue.execute();
         *
         * @example
         * var myQueue = new core.chain();
         *
         * var myFunc1 = myQueue.link(function() {
         *     // do stuff, will be executed first
         * });
         *
         * var myFunc2 = myQueue.link(function() {
         *     // do stuff, will be executed second
         * });
         *
         * var myFunc3 = function() {
         *     // do stuff, will be executed third
         * };
         *
         * var myFunc4 = myQueue.link(function() {
         *     // do stuff, will be executed fourth
         * });
         *
         * myQueue.queue([myFunc1, myFunc2], [true, myFunc3], myFunc4);
         *
         * myQueue.execute();
         *
         * @name core.chain.queue
         * @methodOf core.chain
         * @param {mixed} [1..n] A mixed bunch of parameters that represents the functions to be queued.
         * If the first parameter is true, then the given functions will be bind with the link method,
         * to automatically invoke the next item in the queue. This parameter can be a mixed set of functions
         * and functions inside arrays. They will be processed recursively in a given order.
         */
        this.queue = function() {
            var start = (started === true && queue.length === 0);
            init(arguments);
            if (start === true) {
                invoke(0);
            }
            return this;
        };
        
        /**
         * Starts processing the queue
         *
         * @example
         * var myQueue = new core.chain();
         *
         * myQueue.queue(true, function() {
         *     // do stuff
         * });
         * 
         * // Starts the queue process with 2000ms throttle
         * myQueue.execute(2000);
         *
         * @example
         * var myQueue = new core.chain();
         *
         * myQueue.queue(true, function() {
         *     // do stuff
         * }).execute();
         *
         * @example
         * var myQueue = new core.chain().execute();
         *
         * myQueue.queue(true, function() {
         *     // This function will be processed immediately,
         *     // as the execute was issued when creating the instance
         * });
         *
         * @name core.chain.execute
         * @methodOf core.chain
         * @param {number} [delay] Throttle delay in milliseconds
         * @returns {object} This object instance
         */
        this.execute = function(delay) {
            execute(delay);
            return this;
        }
        
        /**
         * Pauses the current queue process
         * 
         * @example
         * var myQueue = new core.chain().execute();
         * 
         * myQueue.queue(true, function() {
         *     // do stuff
         * });
         * 
         * myQueue.pause();
         * 
         * myQueue.queue(true, function() {
         *     // do stuff
         * }, function() {
         *     // do more stuff
         * });
         * 
         * // Lets wait 5 seconds and resume the queue again
         * setTimeout(function() { myQueue.resume(); }, 5000);
         *
         * @name core.chain.pause
         * @methodOf core.chain
         * @returns {object} This object instance
         */
        this.pause = function() {
            started = false;
            return this;
        };
        
        /**
         * Resumes the current queue process
         * 
         * @example
         * var myQueue = new core.chain(true, function() {
         *     // do stuff first
         * }).execute();
         * 
         * myQueue.pause();
         * 
         * var arr = "Bananas Apples Pears".split(" "), funcs = [];
         * 
         * for (var i = 0; i < arr.length; i++) {
         *     var item = arr[i];
         *     funcs.push(function() {
         *         alert("I like " + item);
         *     });
         * }
         * 
         * // resumes the queue with 2000ms throttle
         * myQueue.queue(true, funcs).resume(2000);
         * 
         * @name core.chain.resume
         * @methodOf core.chain
         * @param {number} [delay] Throttle delay in milliseconds
         * @returns {object} This object instance
         */
        this.resume = function(delay) {
            execute(delay);
            return this;
        };
        
        init(arguments);
    };
    
    core.namespace("chain", chain);
})(window[CORE_NS]);
